All right.
In this lecture, we're going to be scripting the I, Squidward that will randomly roam around the map
and chase our players.
This means we'll have to utilize something called the Pathfinding Service to generate waypoints between
two different positions that our AI can walk to.
If our AI sees a player directly, then our AI can just chase those players without needing to pathfind.
Our AI is also going to need to attack or jump scare any players that it gets close to, and it also
needs to be aware of players that are inside of the lockers or have already escaped out of the basement.
That way those players don't accidentally get jump scared to get started, we're going to go ahead and
open up some module scripts we're going to need to script.
So we can go inside of server script service and open up the module section, and we can go ahead and
open up our game sections, module script.
And we're also going to open our AI Squidward Handler module script as well as the properties module
script for that module script.
And then we're also going to go on to the client, and we're going to open up the camera manipulation
handler as well as the jump scares module script.
Now inside of our game sections module script.
Basically, the plan is once we start section two of our game and the players go through that beginning
cut scene.
After we've set everything up and we're waiting for them to open up that door in the basement, what
we could do is we could wait like five seconds after the cut scene to give them a little bit of breathing
room, and then we can start our AI, and that's where we would use our AI, Squidward handler.
And we could have a function in there to start the AI.
So real quick we could create a variable up top here, and I'll just call this, uh, Squidward AI.
Handler and will require from server script service dot server dot modules dot Squidward dot I Squidward
handler.
And then we could go back down and we can refer to our Squidward I handler, and we could have a function
in there, like to start our AI.
Something like that.
So inside of our I, Squidward handler, what we could do is we could create the table that will represent
this module script.
We'll just call it I handler.
And we'll make sure to return I handler at the end of our script.
And then inside of here, we can have a function in our AI handler to start our AI.
And we can also have a function to stop our AI.
Now for our I.
What we have to do is we have to have a while loop that constantly loops through and allows the AI to
continue wandering around.
And when the AI sees a player to attack, it will chase after the player.
Otherwise, if it doesn't see any players at all, it'll just keep wandering around our map.
And the way we can do this is that inside of server storage and our other stuff folder, we have two
different folders, one called Squidward spawns and one called Squidward Rock points.
And this folder contains a bunch of different points or positions around our map that our Squidward
can pathfind to.
So when our Squidward is wandering around the map, we're going to basically have it spawn at one of
these Squidward spawn points, which are inside of the ceiling.
So above our basement we have like the ceiling area where our Squidward can spawn at, and then he can
fall through different points in the ceiling.
So up here, there's these different pass through parts in the ceiling where our Squidward will spawn
up here inside of the ceiling, and then fall down into the basement and start pathfinding.
After a certain period of time where our Squidward has been pathfinding for a while, but it hasn't
seen any players.
What we can do is we can make our Squidward disappear, and then respawn him and put him at a different
position, or a different Squidward spawn and have him fall out of the ceiling at that area instead.
And then have them continue pathfinding until he sees a player.
Now, because our AI is going to be running in a while loop, that means when we start our AI, we're
going to have to spawn it in a different thread.
So we could have a private function actually, and we could call it like start AI, and we could spawn
this function in a new thread when we call the start AI function from our game sections.
Because again, we don't want to be stuck in a while loop here.
Otherwise we won't be able to do any of this other stuff when the thread of execution is stuck looping
forever inside of here.
So inside of our I, Squidward handler, we're going to need some services.
One is going to be service.
Storage, of course.
We are going to need replicated storage.
We're also going to need the player service.
We'll also need the team service.
And then we're also going to need the pathfinding service.
And I'll just call it pathfinding.
And it's equal to game get service.
Pathfinding service.
From this point, we're going to make a references to a couple events.
One is we're going to make a reference to the game comms event.
So that's unreplicated storage, dot events, dot remotes, dot game communication.
And that means we're going to have to get the game comms enum for this event.
So we'll require from replicated storage dot modules dot enums dot game communication enum.
We're also going to need our update UI event replicated storage, dot events, dot remotes, dot update
guy.
And then we can get the guy actions enum as well.
And we're going to be using this event here to tell the players to display a jump scare on the screen.
And we're also going to be using this as well to tell a players to display the death frame.
So when the players get jump scared after the jump scare is over, we want to tell them that, you know,
they died.
So they fade the died screen, you know, onto their screen.
And then we put them back into the spawn area.
And from that point the players can begin to spectate.
We're also going to need the camera manipulation event so we can call it manipulate cam event, replicated
storage, dot events, dot remotes, dot manipulate camera.
And then we're also going to need the camera manipulation enum.
And we'll acquire from replicated storage dot modules dot enums dot camera manipulation enum.
We'll make a reference to our AI, Squidward itself.
It's very important.
And that's inside of server storage, inside of the other stuff folder.
And we can get our handsome Squidward AI.
We're also going to want the properties for our Squidward so we can call this AI properties, and that's
going to be inside of the script dot properties.
We'll make a reference to the humanoid of our AI so we can call it My Humanoid, and that's equal to
I Squidwards humanoid.
And we're also going to get the root part so we can call it my root part equal to I.
Squidward dot humanoid root part.
We're also going to make a reference to the body of our Squidward.
So that's a Squidward dot body.
And we're also going to make a reference to the Squidward eyes.
So attach to our Squidward if I get him outside of our other stuff folder, let me find them real quick.
Here's our handsome Squidward I.
Put him inside of the workspace.
We'll find him.
As you can see, we have his eyes up here.
And what we're going to use his eyes for is we have a part in here called the raycast part, and we're
just going to fire a ray from that position to try to see if our Squidward is able to see any players.
This way.
It stops our Squidward from chasing or trying to see players that are behind walls and stuff like that.
So we want to make sure that the player is first within range of our Squidward, and we actually want
to make sure that our Squidward can actually see the player as well.
So we'll go ahead and put them back inside of his other stuff folder.
And we'll call this variable eyes.
And that's equal to I, Squidward dot eyeballs.
We're also going to make a reference to all the different walking points, or the points that our Squidward
can pathfind to in the map, so we can call it Walk to Points, and that's instead of server storage.
That other stuff dot Squidward Walk points and we'll get all those children.
And we're also going to want to get all of the different spawn points so we can call it spawn points,
and that's inside of server storage.
Dot other stuff that Squidward spawns, and we'll get all of the children inside of there as well.
We'll also want a random data type.
And then we're also going to want some variables to keep track of some specific stuff.
So for example, we want to be able to have our Squidward create footstep sounds.
That way our players know or can listen for when our Squidward is nearby.
So I'll have to set up the footsteps for him, and we only need to do it once so we can have a variable
we could call it is footstep Setup.
To keep track of whether or not we have set up the footsteps or not.
We also need a variable to keep track of whether or not our AI is active.
So we could call it active and we'll set this to false by default.
So when we start our AI, we'll set this to true.
And then when we stop our AI we'll set this to false.
And our while loop is going to constantly check whether or not we are active.
If we're not active, then we'll stop.
You know the AI, but if we are active, then we will continue wandering around the map and looking
for players.
We also need a variable to keep track of whether or not our Squidward can attack or jumpscare players,
and we'll set this to true by default.
So when our Squidward can attack, this will be true.
And then we'll set it to false for a little bit of a cooldown.
So that way our AI can't attack every 0.5 seconds or whatever.
And then we all need a variable to keep track of the amount of wandering iterations.
So each time our Squidward path finds around the basement, every time he hits a pathfinding point,
we're going to plus one.
Add that to his wandering iterations, and then he's going to go wander to a different point.
We'll add plus one to the wandering iterations, and this is to keep track of how long our Squidward
has been wandering for.
So if our Squidward has wandered to multiple different points, let's say it gets above five.
Then we're going to be like, huh?
This Squidward hasn't seen any players, and he's been wandering to five different points.
Maybe we should respawn him and let him pathfind in a different area of our basement, so we can call
this variable Wandering iterations.
And we'll set this to zero by default.
And then we can also keep track of all the different points that our AI has already visited, so we
can call this points already visited.
And this will just be equal to a table.
And that way our Squidward doesn't accidentally visit the same point multiple times in a row.
Okay, so what we want to do when we want to start our AI is first, we want to refer to the humanoid
of our AI and we want to set state enabled.
Enum dot humanoid state type dot falling down equal to false.
This is the absolute first thing we want to do.
And this is because when we spawn our Squidward or UN, anchor him or move him around, there's a chance
he might fall over and we don't want that to happen.
So to stop our Squidward AI from ever being able to fall over or fall down, we set the state enum falling
down to false so he is never able to fall down.
From this point, we need to set our AI Squidward dot parent equal to the workspace so he's visible
to all of our players.
And then we also need to set the root part of the network ownership equal to the server.
This way, no player is able to have network ownership over our Squidward, and that way exploiters
can't do any silly things with our Squidward.
Another thing we will want to make sure real quick is we want to set inside of the humanoid.
We want to set the hip height equal to 2.4 studs.
That way, the feet of our Squidward is about flush to the ground.
So if I go ahead and set the parent of our Squidward equal to the workspace and we go find him here.
Currently, I believe he has a hip height of 2.4 studs, but we can change this.
And if we do something like one, that means when he is unanchored and he goes and walks around, half
of his body is going to be in the floor.
So he's going to be like walking around kind of like this, and this is how he's going to be walking
around, which won't look nice.
So if we set his hip height to 2.4.
Then he's going to be perfectly flushed to the ground about at this height.
And this is how he's going to look when he's walking around.
It looks much nicer.
So we'll put him back inside of server storage.
And we'll make sure to set the hip height at 2.4 studs.
And after we set the hip height, this is where we will want to set up the footsteps for our Squidward.
So what we could do is we could have a function.
And I'll call this function.
Set up footsteps.
And we can call this function down here.
And this is where we will check if we have set up the footsteps or not.
So if is footsteps set up, if that's true, then we're just going to return.
Otherwise from this point is where we would start to set up the footsteps for our Squidward.
And if you don't want our Squidward to have footsteps, then we could create a property inside of our
Squidward to determine whether or not he can have footsteps.
So, for example, in here we could create a key value pair and we can call this has footsteps.
And I want my Squidward to have footsteps.
So I'll set this equal to true.
And what we could do from here is we could refer to our I property.
So if I properties.
Dot has footsteps.
So if we do not want to have footsteps then we'll just return out of this function.
Otherwise what we could do is we could set is footstep setup equal to true.
And what we want to do is we want to create some variables here.
We'll call this variable last material.
We'll set this equal to nil.
We'll also want a variable to check the last position of our Squidward.
And we also want a variable to keep track of the cooldown between footsteps.
And we'll set this to 0.45 seconds as default.
And we're also going to want to make a reference to the sound that's inside of the body of our Squidward,
so we could call this footstep sound, and that's inside of our body.
And there's a sound in there called footstep.
Inside of here.
We're also going to want to create another function.
And I'm going to call this function check movement.
And this is to check whether or not our Squidward is moving.
And we only want to play footsteps when he's, you know, moving around.
Then inside of here, what we could do is we could spawn a while loop and a new thread so we could use
tasks dot defer to spawn a new function, and basically we'll have a while true do loop in here.
And what this does is it'll check whether or not our Squidward is moving.
And if our Squidward is moving, then he'll play a footstep.
So while true, do if we call our check movement function.
If that value returned is true, then what we would do is we would just play our footsteps sound.
Now to add some variety to our footsteps sound.
There's actually a pitch shift in there, a sound effect.
So inside of the footsteps sound there is a pitch shift sound effect.
And we can change the octave of the sound effect and use our RNG next number.
And we could pass a value between anywhere between 0.9 and 1.1.
The default is one.
So if we go to our handsome Squidward AI and inside of the body, here's our footsteps sound with a
default pitch of one.
Our Squidward footsteps sounds like this.
But if we, you know, reduced the octave by like 0.9 and we play it now, then it sounds a little bit
deeper and then we can also increase it.
That way.
You know, footsteps don't always sound the exact same.
We add a little bit of variety to the pitch of our footstep, so we play that footstep if we're moving.
And then what we would also want to do is we want to refer to our last position and set that equal to
the my root part dot position.
And you'll see why we're doing this in a moment.
Now just in case this footstep sound was already playing before we actually play the footstep sound
again, we also want to make sure to stop our footstep sound, and we also want to make sure that we
actually set our last position before we start this while loop equal to the my root part dot position.
So basically what we're going to do in this check movement function is we're going to get the last material
that our humanoid was standing on.
So we're it's going to be equal to my humanoid.
And there's a property called Floor material.
And we want to make sure that this is in air.
Right.
We don't want to play footstep sounds when our AI, Squidward, is in the air.
That doesn't make any sense, right?
So we want to have an if statement here, and we want to check.
First off, if my humanoid dot walk speed is greater than zero.
And we also want to make sure that my root part dot position, and we want to subtract this by our last
position and get the magnitude between those two positions is greater than or equal to something like
three studs.
So this actually verifies that, you know, we're moving around the map if this is less than three studs
or it's zero studs or whatever, then that signifies that our humanoid is still relatively in the same
position.
And because we know that then our humanoid hasn't actually moved around.
So we're going to, you know, not play a footstep sound.
So if our walk speed is greater than zero and our magnitude is greater than or equal to three studs.
And we also want to make sure that the last material that we were standing on isn't equal to null.
And we also want to make sure that the last material we were standing on is not equal to the enum dot
material, dot air.
If that's the case, then we can go ahead and return true because our walk speed is greater than zero.
We've actually moved and we're actually standing on ground and we're not standing on air.
Or last material isn't equal to nil.
So this signifies that we're actually moving.
And because we're moving then we can go ahead and play footstep sound.
If this isn't the case, then otherwise we can just return false to signify that we're not moving.
Now, another thing we're going to want to keep track of is when the walk speed of our humanoid changes.
And this is because we're going to have two different walk speeds.
We're going to have a walk speed for when our AI is just wandering around the map.
And then we're also going to have a chase speed.
So when our AI sees the player, he starts running towards them.
And when that walk speed changes, then we obviously want to change the cooldown between footsteps in
our while loop.
So after we play a footstep, we'll have a wait, which is our cooldown, which means we're going to
want to listen to when our My Humanoid Get property change signal.
We want to pass the walk speed we want to listen for when this changes.
So for example, if our my humanoid walk speed, let's say it becomes greater than uh, the walk speed
defined within our AI properties.
So if we go in our AI properties, we can have a wandering speed.
And we could also have a chase speed.
So when our eye is chasing a player, this is the walk speed we want to set for them.
When our eye is just wandering around, this is the speed we want to set for them.
So for example, for our wandering speed we could just do like eight studs.
So he's just casually strolling around.
But for our chase speed we could do something like 22 studs per second.
Of course, we want this to be slightly lower than our player sprint speed.
That way our players actually have a chance to run away from our Squidward.
But what we could do is we could check if our humanoids walk, speed becomes greater than our AI properties.
Dot wandering speed.
If that's the case, then we want to set the cooldown value equal to 0.25.
So we reduce that cooldown.
Otherwise, we can just keep the cooldown at the default of 0.45 seconds.
And this should allow us to create audible footsteps for our players.
That way they can actually listen for when our Squidward is nearby.
After this point, since we've set the parent of our I, Squidward equal to the workspace as you saw
earlier, he spawns up here and we need to actually go ahead and move him at to one of those spawn points
that are defined within our server storage.
So what we could do is we could create a function and we could call it something like disappear and
spawn a new location.
So we could call this function disappear and spawn at new location.
And we can use this function whenever we want to have our Squidward disappear and have them reappear
at a new spawn location.
So for example, if our wandering iterations go beyond, let's say five, then we can have them disappear
and spawn out a new spawn location and start pathfinding from that point.
So to have them disappear and spawn at a new location, first we want to refer to a particle emitter
that is inside of the body of our Squidward, and we want to emit a crap ton of particles.
We could do something like 300 this way.
It kind of masks him, and it doesn't make it look like you know, he disappeared out of thin air.
So actually, you know, let's just keep our handsome Squidward AI inside of the workspace.
So we'll set his parent equal to the workspace.
We'll go back to them and inside of his body he has a particle emitter.
And we can emit something like 300 particles.
And it kind of helps mask him to make it look like, you know, he disappears.
So after the particles disappear, he's no longer there.
So after we emit a crap ton of particles, there's a sound inside of his body called poof.
And we'll just play that to sound like, you know.
Oh.
He disappeared.
So inside of the body of our Squidward, there's a sound called poof, and we'll just play that sound.
And after we do that, we can wait for, like, one second.
And we could go ahead and have them spawn at a random spawn point.
So we'll get a random int from our RNG next integer, and we're going to do one to the maximum number
of our different spawn points.
And what we could do is we could refer to my root part and set the key frame.
Equal to a random C frame.
So inside of our spawn points table we'll pass our random integer.
And then just get the C frame of the part that's, you know, we randomly chosen.
And after he's at this new spawn location, what we could do is we could refer to our My Humanoid.
And what we want to do is we want to move him to a new position.
And the snoop position we want to move him to is inside of the spawn points.
Pass the random int again, so we refer to the exact same part, because inside of those different spawn
points is we have a attachment inside of them, and this attachment is called walk to point.
And we can get the world, see frame of that attachment and get the position that we want him to walk
to.
So if we go inside of server storage.
Here we go to Squidward spawns.
We have different walk.
Two points.
So for example, here's one where he spawns up here, goes to the ceiling and walks down, or basically
falls through this hole in the ceiling to this position here.
And that way he can actually, you know, get inside of the map and start pathfinding around the place.
So we'll move him to this position.
And then what we want to do is we want to wait for him to finish moving to this position.
So that means we're going to want to wait for the move to finish event inside of our My Humanoid.
And in order to do that, what we could do is we could go to our game sections module script, and we
can scroll up and we can go ahead and just copy our wait for event function that we did before.
And we could go ahead and put that inside of here.
And we can go ahead and wait for event.
My humanoid move to finish.
And we can have a timeout of like three seconds, and then we can check whether or not we timed out.
So if we timed out, then obviously we ran into a little bit of a problem.
So what we'll have to do instead is we'll just have to make him disappear and then reappear at this
point that we tried to walk him to because he couldn't walk there.
So we'll just have to teleport him there instead.
So what we could do is we could refer to the body dot particle emitter and emit something like another
300 particles.
And then we can play the poof sound in our body again, so body poof will play the sound.
We can wait something like one second to just give enough time for the particles to spread out, and
then we'll refer to our my root part dot position.
And we'll set that equal to the spawn points.
And actually we'll just copy what we did exactly right here.
So we'll just set him to that position directly.
And after we do that again, we'll emit some more particles.
So body dot particle emitter emit something like 300 particles.
And we'll play the poof sound again.
So body dot poof play.
And then after this point, we can go ahead and put a wait statement down here of like one second.
That way our eye doesn't start wandering instantaneously.
So when our eye gets put on the map and he walks to the first point to actually enter into the basement,
we're going to have him sit there for like one second that way, you know, in case he accidentally,
like, falls directly on top of a player, he doesn't kill them instantly because that would be a little
unfair.
So then we can go ahead and call our disappear and spawn a new location function.
So after we set up the footsteps and he's on the map, then we place some inside of the basement and
after this point we can go ahead and start the main loop that will be running our AI.
So we could have a function.
We could call it main.
And this would run forever while our AI is active.
So while active is true, what we would do is we would execute all the functionality for our AI.
So what we could do down here is set active equal to true and then call our main function.
And now our AI is going to be active.
Now when our AI is active the first thing we want to check is whether or not we have a target that we
can attack.
So we can create a variable.
And I'm going to call this target.
And we can have a function to get basically the nearest visible player to our AI.
So we could have a function called get nearest visible player.
And this will return back to us basically a root part of the nearest target that we can attack.
So we can have a function here and we can call this get nearest visible player.
And this will loop through all of the alive players in our game and make sure you know they're not dead,
they're not in a locker.
And it'll also make sure that we can actually see that player and we can actually go and chase them.
I made a little small spelling error.
So let me fix that real quick.
Get nearest visible player.
And after that, what we want to do is we want to create a variable in here.
We'll call it closest.
Target will set this equal to nil for now, and we want a distance to compare the closest target to.
So we could call this last distance.
And by default we're just going to set it to some randomly high number because we don't care.
But basically we want to compare the last target that we found that was close to our eye to the previous
distance.
You may be thinking, what do you mean?
So let's say there's three different players around our eye.
Well, our eye needs to figure out out of those three players which one is the closest.
So it needs to compare the distances between all those different players.
And it basically needs to go through the process of elimination and figure out which player is closest.
Right.
So we'll loop through every single player that is in our alive team.
So we'll get every single player in there.
And we want to do is we want to first make sure that this player is alive and they're not inside of
a locker, or they don't have a character or whatever.
So if this player does not have a character.
Or let's say this player does not have a root part for some reason.
Or let's say this player character humanoid get their health.
Let's say that's below one.
Or basically they're dead, right?
Or let's say this player is inside of the lockers.
So if player get attribute inside lockers true, then we want to ignore this player as well.
Or we also want to make sure that this player hasn't escaped from our game.
Remember we set an attribute on them when they escape the basement of escaped.
So if that's true, we don't want to care about this player either.
So basically.
If this player doesn't have a character or they're dead, or they're in a locker or they've already
escaped, then we just want to continue looping because we don't care about this particular player.
Otherwise, if they pass all this criteria, then we need to check their distance away from our eye.
And we also need to check whether or not they're visible so we can create a variable.
I'm going to call this new target.
And we're not going to initialize anything just yet because we want to first check the visibility of
the root part of this player.
So we could create another function.
And we can call this function something like check visibility.
And we would get passed a target here which would be a base part.
And then we could also have a boolean pass to this.
And I'm going to call this with Dot.
And this is because we're going to use this boolean to determine whether or not we should calculate
this visibility using vector three dot or not.
And you might be thinking what's vector three dot?
Well, if you haven't heard of vector three dot, what it does is it returns a scalar product of two
vector threes.
What does that mean?
Well, if I create a new vector three here let's just do 111 and I use a dot.
It returns a scalar dot product of the two vectors.
So that means you would pass another vector three to this function vector three dot new.
And you could do something like negative one, negative one negative one.
And this function will tell you basically how similar are these two vector threes.
Are they both looking in the same direction or not?
Since these two vector threes are exact opposites, then this function would return a value of negative
one to signify that this vector three is facing the exact opposite direction of this vector three because,
as you know, a vector three is basically just a direction in 3D space starting from the origin point.
So we can go ahead and compare what direction they are facing.
And I'll show you this example right now if I copy this, and I were to print out this value here,
and we actually need to convert this vector three to a normalized copy, or basically a vector three
that has a magnitude or length of one stud.
And we need to do that for both of these vector threes.
And if I print this value out here.
It's like I got a little bit of an error if I print this out.
There we go.
We basically get a value of negative one.
And this is telling us that, hey, these two vector threes are facing the exact opposite direction.
If I were to do the exact same vector three, then we're going to get a value of one, which signifies
that these two vector threes are facing the exact same direction.
And you might be thinking, well, what's the purpose of this?
Well, the usefulness of this function is we can determine whether or not a player is within the field
of view, or basically the pseudo field of view of our eye.
Right.
We can determine or we can set a threshold for when our eye can no longer see any players.
So to explain this more easily, I'll give an example here.
Let's just say this is the head of my eye and this is the direction that he's facing.
Right.
We can get this direction.
And let's just say it has a length of one.
And then let's say we also have another player on our map, like over here.
What we're going to do is we're going to basically get the direction from our head to this player.
So we would calculate.
That direction.
Right.
And then we would compare this vector three to the vector three of where our eye is facing.
And we would check the difference.
Now because these two vectors would be basically facing the opposite directions, we would know that
this player right here is not within the field of view of our eye.
But let's say we had a player over here and then we calculated the direction to this player.
Well, then we would figure out, hey, these two vector threes are pretty similar.
We could set a threshold.
Maybe the value of this is like 0.5.
The, uh, a value or basically a vector three that would be perpendicular to this would be zero.
And a direction that's exactly opposite would be negative one.
So we could set a threshold of like 0.2.
So that way when we calculate the dot product of these vector threes, so any that fall between 1 and
0.2 means that they're basically within the field of view.
You can imagine this imaginary field of view of our eye.
This player right here falls within that field of view.
And because they do that means our eye can go and attack them.
And that's the power that we can use with the dot function.
So inside of this function, what we could do is we could check if width dot is true.
If it is, then we need to calculate it.
So what we're going to do is we're going to make several variables.
One variable I'm going to make is called uh direction to target.
And this is the vector three starting at our eyes head aiming towards the target that we want to look
at.
So basically what we would do is we would get the target's position.
And subtract it by the origin point, we would want to have this direction start at, which would be
our my root part dot position.
And we want to get the unit or normalized copy of this vector three.
So now that we have this direction that's towards our target starting at our root part, we want to
compare this with the look direction of our root part.
So what we could do is we could create a variable I'm going to call this look direction.
And this is just going to be equal to my root part dot c frame.
And we get the look vector of that c frame.
So here we have our root part and the direction that it's facing.
And here we have this other direction that's towards our target.
And we want to compare these two directions to see how similar or unsimilar these two directions are.
So we can get the dot product.
By referring to our direction to the target and using the dot function, and we could pass our look
direction, and this will return a value to us of, you know, how similar are these two vector threes.
And basically what we could do is we could have a property inside of our self table here.
And we can call this property something like minimum dot product.
And I'm going to set this to something like 0.1.
So basically our eye will have a pretty wide field of view.
But basically any players that are I guess within this kind of field of view of our eye is going to
be seen and attacked.
So you can imagine this direction here and this direction here.
This is where our eye is facing.
Comparing this direction to this direction is a value of zero.
And the value of 0.1 would be like something over here.
So basically he has a pretty wide stinking field of view.
Of course you could adjust that number to whatever you like, but this should work pretty well for what
we're doing.
So we can go ahead and check to see if our dot product is less than our I.
Properties dot minimum dot product.
If it is, then this player is not in the field of view of our I and we're just going to return false.
Otherwise that means we have successfully passed the dot calculation.
Now another thing I actually want to do real quick is I want to basically restrict this direction to
the x and z axis and not the y axis, because let's say our player is high up or high down, right?
If our player is super low down or player is super high up, then that's obviously going to affect our
dot product.
But I only want to compare the player's position on their x and z axis.
So what I could do is I'll create a new direction.
And is going to be equal to a vector three dot new.
And all we're going to do is get the direction to our target on the x axis.
We're going to ignore the y axis and then get direction to the target on the z axis, and get a normalized
copy of that direction.
So basically I want to ignore any direction in the y axis because I don't care about the altitude of
our player.
I just want to make sure that our player is within the field of view of our eye on the x and z axis,
and then we can use this new direction instead for the calculation of our dot product.
So we could do new direction.
Dot and compare it to the look direction, and this will give us a pretty accurate dot product.
If we surpass the dot product, meaning the player is within the field of view of our eye, then the
next thing we want to make sure is our eye can actually see them by firing a ray from our eyes eyeballs
to see if it hits that player.
So I'm going to do is I'm going to create a parameters for a new raycast.
So raycast params dot new.
By this point you should be quite familiar with Raycasting.
But if you aren't familiar with Raycasting, well, it's pretty simple.
We create parameters for our raycast and inside of this parameters we can filter out specific instances
that we want to ignore, and in this case, the different instances that we want to ignore is basically
everything within our eye.
Squidward.
So that way we don't want the ray to hit any part in our Squidward, and we also want the ray to ignore
any part that is inside of the workspace.
That filtering folder which stores all of the invisible parts in the workspace.
And then we can set the params dot filter type.
Equal to the enum dot raycast filter type, and we want to exclude these different instances.
So that way our raycast ignores them and the ray goes directly through them.
And then from this point we can calculate the direction for this raycast.
And that's going to be where we get our target.
And we're going to get their position.
And the origin point for this array is going to be our eyeballs.
So inside of the eyes of our Squidward, we have that raycast part.
And we want to get the position of that.
And then we also want to get a normalized copy of this direction.
So that way it's only one stud.
And then from this point we can multiply it by some number to independently control how long we want
this direction to be.
So from this point we can use the workspace raycast function.
And the origin for this raycast is going to be the eyes dot raycast part dot position and our direction
is going to be equal to the direction we just calculated.
And we can multiply this by a distance that we can define inside of our properties table.
So inside of our properties table we could create another property like we could call it targeting distance.
And this will determine in studs how far away our eye can see and target.
You know a player.
And we could do 150 studs.
That'll probably be fine.
So we'll multiply this by our eye properties dot targeting distance.
And the last thing we want to pass to the raycast function is our raycast parameters that we just created.
So params.
And this function will return to us a result.
And we can check to see if we hit anything.
So if we did hit something, if there was a result, and we want to check the instance of the thing
that we hit.
So this will tell us the base part or terrain cell that the ray intersected.
So if this instance is a descendant of so there's a function called is descendant of and we pass our
target dot parent.
So basically since our target is the root part of the player, that means the parent of that root part
is going to be the player's character.
So if the instance that we hit is a child or descendant of the player's character, then that means
we hit the right target.
The next thing we want to check is if the target dot position on the y axis is less than or equal to
the, uh, position of our root part on the y axis.
And why do we want to check this?
Well, this will tell us if our player is for some reason below our, uh, I if that's the case, then
that's great.
Our I can go ahead and get to the player and we can return true here.
However.
Let's say for some reason, uh, somehow our player is super high up there, like standing on, like
a building or something.
Of course, this is code you could use for different AI later on, and that's why we're making this
check real quick.
But if this player is like way too high up, then our AI is not going to be able to pathfind to this
player.
And we want to ignore this player.
So we want to do is we want to basically, uh, get our target dot position.
On the y axis and subtract this by my root part dot position on the y axis.
And we just want to get the absolute value of this.
So math dot absolute.
And we want to basically compare this to a number that we can define in our properties as well.
So inside of our properties we could create a property like max visibility height.
And this will define in studs how high up a player can be or the maximum height that they can be where
our uh where our eye can get to them.
And I found eight studs to be good.
When a player gets above eight studs in height above the root part of our Squidward, then we'll start
running into some problems because our AI can't properly pathfind to that player.
They're too high up.
So for some reason, the position of our target is greater than our AI properties.
Dot max visibility height.
Then we're just going to return false because we don't care about this player.
They're too high up otherwise.
In any other case, we're just going to return true because we are good to go.
However, if we did not get a result from our raycast then we are going to return false meaning this
player could be behind a wall.
Maybe they're hiding behind something.
Whatever the case may be, our AI does not see this particular player, so we're going to return false.
The back and side of our get nearest visible player function.
What we can do is we can check the visibility for our player character, that humanoid root part.
And we want to check with the dot product to make sure that they're in the field of view of our eye.
So we're checking the visibility of this player.
If this is true, they are visible.
Then we can set new target equal to the player character.
That humanoid route part.
Otherwise.
Another thing I want to check for is let's say, um, our player is like really close to our AI.
So, for example, let's say the AI is not facing them, but the player is following really close behind
them.
Well, I don't want players to be able to do that.
If a player gets too close to our AI, I want our AI to start chasing them, no matter if they're behind
them or in front of the AI.
It doesn't matter if they're in the field of view.
So what we could do is we could check if the player dot character dot humanoid.
Route part.
That position subtracted by my route.
Part dot position dot magnitude.
We want to check if this is, uh, less than or equal to a another value that we can define within our
properties table.
So we could create another key in here.
We could call it something like sensing range.
And this will define in studs basically, uh, how far away or how close a player can be sensed by our
AI without our AI even having to, you know, have the player in the field of view, right?
And I found 20 studs to be pretty good.
So if our player is like walking or falling behind our AI, if they get too close, then our AI is going
to sense that they're there and our AI is going to flip around and start chasing that player.
So if the magnitude between, um, these two different positions.
So actually let me readjust that.
So we get the magnitude between these two different positions.
If that is less than or equal to the AI properties dot sensing range, then we can go ahead and set
new target equal to the player character.
That humanoid route part.
Now, another thing we want to make sure of is that our AI can still see the player using the raycast,
because this is only checking whether or not the player is within 20 studs of our AI, and our AI might
be walking down a hallway, and our player might be on another side of the wall, but since they might
be within 20 studs of our AI, our AI tries to attack them, which wouldn't make any sense, right?
So another thing we want to make sure of is that this player is actually still visible.
So we could pass player dot character dot humanoid Rupert.
But this time we don't want to calculate using the vector three dot product.
So we'll pass false here.
So this will simply calculate whether or not this player is visible by firing a ray from our AI's,
you know, eyes to the root part of our player.
So if that checks out, then we can set our new target equal to this root part.
Otherwise, if nothing checks out at all, then we can go ahead and loop to the next player.
Otherwise.
At this point, if we do have a new target, then what we want to do is we want to check the distance
between, um, uh, our A's root part.
So my root part dot position subtracted by a new target dot position.
Get the magnitude of that.
And we want to check if this distance.
Is less than or equal to our targeting distance, which we shouldn't have an issue with that.
So this is just an extra check just in case.
And we also want to make sure that this distance is less than the last distance that we stored up here.
Because we're going to be looping through multiple different players.
We don't want to pick any old player that's visible and close to our eye.
We want to pick the single player that is the closest to our eye.
Out of all the players on our map.
So we want to set closest target equal to this new target, and we also want to set the last distance
equal to this new distance that we calculated.
So we're constantly updating the last distance here.
That way we're guaranteeing that this closest target variable is actually the closest target to our
eye.
After we loop through all the players and we get our closest target, we can go ahead and return it
at the end of this function here.
And then from this point, we can go back down to our main function and we are able to get the closest
target to our eye.
And from this point, if we have a target, then this is where we would start to chase them.
Otherwise, if we don't have a target, then this is where, you know we would wander the map.
Now, of course, this lecture is getting a little long.
So we're going to go ahead and finish scripting up our AI in the next lecture.
I'll go ahead and see you there.